﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Toolkit.Web.Helpers;
using System.Web;
using System.Web.Mvc;

namespace Toolkit.Web.Mvc.FilterAttributes
{
    public class TransferModelStateErrorsAttribute : ActionFilterAttribute
    {
        public TransferModelStateErrorsAttribute(string modelStateKey)
            : base()
        {
            if (string.IsNullOrEmpty(modelStateKey))
            {
                throw new ArgumentException();
            }

            ModelStateKey = modelStateKey;

            // Note: Keep cookie name small to save characters. Cookies should not be more than 4095 bytes in size.
            CookieName = "RedirectErrors";
        }

        public string CookieName
        {
            get;
            set;
        }

        public string ModelStateKey
        {
            get;
            set;
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // Only try to retrieve model state from cookie if result is a view result (not a redirect result).
            if (filterContext.Result is ViewResult)
            {
                // Try to get model state errors from cookie.
                HttpCookie requestCookie = filterContext.HttpContext.Request.Cookies[CookieName];
                if (requestCookie != null)
                {
                    IList<string> errorCollection = Deserialize(requestCookie.Value);

                    if (errorCollection != null)
                    {
                        // Merge in model state errors from cookie.
                        foreach (string errorMessage in errorCollection)
                        {
                            filterContext.Controller.ViewData.ModelState.AddModelError(ModelStateKey, errorMessage);
                        }
                    }

                    // Expire cookie always.
                    HttpCookie responseCookie = filterContext.HttpContext.Response.Cookies[CookieName];
                    if (responseCookie != null)
                    {
                        responseCookie.Expires = DateTime.UtcNow.AddDays(-1);
                        responseCookie.Value = string.Empty;
                    }
                }
            }
            // Only set cookie if errors exist and user is being redirected.
            else if (((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult)) && !filterContext.Controller.ViewData.ModelState.IsValid)
            {
                // Get errors from model state.
                IList<string> errorCollection = new List<string>();
                foreach (ModelState value in filterContext.Controller.ViewData.ModelState.Values)
                {
                    if (value.Errors != null && value.Errors.Count > 0)
                    {
                        foreach (ModelError modelError in value.Errors)
                        {
                            errorCollection.Add(modelError.ErrorMessage);
                        }
                    }
                }

                // Take out any duplicates to save bytes.
                errorCollection = errorCollection.Distinct().ToList();

                // Set cookie.
                HttpCookie cookie = new HttpCookie(CookieName);
                cookie.Value = Serialize(errorCollection);
                cookie.Secure = filterContext.HttpContext.Request.IsSecureConnection;
                if (filterContext.HttpContext.Response.Cookies[CookieName] != null)
                {
                    filterContext.HttpContext.Response.Cookies.Set(cookie);
                }
                else
                {
                    filterContext.HttpContext.Response.Cookies.Add(cookie);
                }
            }

            base.OnActionExecuted(filterContext);
        }

        private IList<string> Deserialize(string value)
        {
            if (string.IsNullOrEmpty(value)) return new List<string>();
            try
            {
                return JsonSerialization.Deserialize<IList<string>>(value);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private string Serialize(IList<string> values)
        {
            if (values == null || values.Count == 0) return string.Empty;
            return JsonSerialization.Serialize<IList<string>>(values);
        }
    }
}
